#include "../include/mip.h"

ILOSTLBEGIN
typedef IloArray<IloNumArray> NumMatrix;
typedef IloArray<IloNumVarArray> NumVarMatrix;

void calCrossproduct(vec &v1, vec &v2, vec &nv){
        nv.x = v1.y * v2.z - v1.z * v2.y;
        nv.y = v1.z * v2.x - v1.x * v2.z;
        nv.z = v1.x * v2.y - v1.y * v2.x;
}

vector<vec> lutApproximation(const pair<int, int> startpt, const int x_width, const int y_width, 
                             LibParserCellInfo &cell, string &fromPin, const int mode)
{
        //since the table is 7 X 8, we first check whether the input is valid
        assert(startpt.first + x_width <= 6);
        assert(startpt.second + y_width <= 7);
        
        bool valid;
        vector<vec> myVectors;
        vec p1, p2, p3, p4, v1, v2, nv, final_vec;
        vector<LibParserTimingInfo>::iterator it_lbpt;
        
        for(it_lbpt = cell.timingArcs.begin(); it_lbpt != cell.timingArcs.end(); it_lbpt++)
                if(it_lbpt->fromPin == fromPin)
                        break;
        
        assert(it_lbpt != cell.timingArcs.end());
        assert(mode == 0 || mode == 1);
        
        // 2 for delays, 2 for transitions
        if(mode == 0){
                //_______________________________Delay______________________________________
                //point 1
                p1.x = it_lbpt->fallDelay.loadIndices[startpt.first];
                p1.y = it_lbpt->fallDelay.transitionIndices[startpt.second];
                p1.z = it_lbpt->fallDelay(p1.x, p1.y, valid);
                assert(valid == true);
        
                //point 2
                p2.x = it_lbpt->fallDelay.loadIndices[startpt.first];
                p2.y = it_lbpt->fallDelay.transitionIndices[startpt.second + y_width];
                p2.z = it_lbpt->fallDelay(p2.x, p2.y, valid);
                assert(valid == true);
        
                //point 3
                p3.x = it_lbpt->fallDelay.loadIndices[startpt.first + x_width];
                p3.y = it_lbpt->fallDelay.transitionIndices[startpt.second];
                p3.z = it_lbpt->fallDelay(p3.x, p3.y, valid);
                assert(valid == true);
        
                //point 4
                p4.x = it_lbpt->fallDelay.loadIndices[startpt.first + x_width];
                p4.y = it_lbpt->fallDelay.transitionIndices[startpt.second + y_width];
                p4.z = it_lbpt->fallDelay(p4.x, p4.y, valid);
                assert(valid == true);
        
                //Plane 1
                //vector 1
                v1.x = p2.x - p1.x;
                v1.y = p2.y - p1.y;
                v1.z = p2.z - p1.z;

                //vector 2
                v2.x = p3.x - p1.x;
                v2.y = p3.y - p1.y;
                v2.z = p3.z - p1.z;
        
                //cross product v1 X v2
                calCrossproduct(v1, v2, nv);
                //Plane formula a(x-x0)+b(y-y0)+c(z-z0)+d=0
                //Let x0,y0,z0 be one of the three points, then d = 0.
                final_vec.x = -1.0 * nv.x / nv.z;
                final_vec.y = -1.0 * nv.y / nv.z;
                final_vec.z = -1.0 * final_vec.x * p1.x + (-1.0 * final_vec.y * p1.y) + p1.z;
        
                myVectors.push_back(final_vec);
                
                //Plane 2
                //vector 1
                v1.x = p4.x - p2.x;
                v1.y = p4.y - p2.y;
                v1.z = p4.z - p2.z;

                //vector 2
                v2.x = p3.x - p2.x;
                v2.y = p3.y - p2.y;
                v2.z = p3.z - p2.z;
        
                //cross product v1 X v2
                calCrossproduct(v1, v2, nv);
                //Plane formula a(x-x0)+b(y-y0)+c(z-z0)+d=0
                //Let x0,y0,z0 be one of the three points, then d = 0.
                final_vec.x = -1.0 * nv.x / nv.z;
                final_vec.y = -1.0 * nv.y / nv.z;
                final_vec.z = -1.0 * final_vec.x * p2.x + (-1.0 * final_vec.y * p2.y) + p2.z;
                
                myVectors.push_back(final_vec);
        }
        else if(mode == 1){
                //_______________________________Transition______________________________________        
                //point 1
                p1.x = it_lbpt->fallTransition.loadIndices[startpt.first];
                p1.y = it_lbpt->fallTransition.transitionIndices[startpt.second];
                p1.z = it_lbpt->fallTransition(p1.x, p1.y, valid);
                assert(valid == true);
        
                //point 2
                p2.x = it_lbpt->fallTransition.loadIndices[startpt.first];
                p2.y = it_lbpt->fallTransition.transitionIndices[startpt.second + y_width];
                p2.z = it_lbpt->fallTransition(p2.x, p2.y, valid);
                assert(valid == true);
        
                //point 3
                p3.x = it_lbpt->fallTransition.loadIndices[startpt.first + x_width];
                p3.y = it_lbpt->fallTransition.transitionIndices[startpt.second];
                p3.z = it_lbpt->fallTransition(p3.x, p3.y, valid);
                assert(valid == true);
        
                //point 4
                p4.x = it_lbpt->fallTransition.loadIndices[startpt.first + x_width];
                p4.y = it_lbpt->fallTransition.transitionIndices[startpt.second + y_width];
                p4.z = it_lbpt->fallTransition(p4.x, p4.y, valid);
                assert(valid == true);
        
                //Plane 1
                //vector 1
                v1.x = p2.x - p1.x;
                v1.y = p2.y - p1.y;
                v1.z = p2.z - p1.z;

                //vector 2
                v2.x = p3.x - p1.x;
                v2.y = p3.y - p1.y;
                v2.z = p3.z - p1.z;
        
                //cross product v1 X v2
                calCrossproduct(v1, v2, nv);
                //Plane formula a(x-x0)+b(y-y0)+c(z-z0)+d=0
                //Let x0,y0,z0 be one of the three points, then d = 0.
                final_vec.x = -1.0 * nv.x / nv.z;
                final_vec.y = -1.0 * nv.y / nv.z;
                final_vec.z = -1.0 * final_vec.x * p1.x + (-1.0 * final_vec.y * p1.y) + p1.z;
        
                myVectors.push_back(final_vec);
        
                //Plane 2
                //vector 1
                v1.x = p4.x - p2.x;
                v1.y = p4.y - p2.y;
                v1.z = p4.z - p2.z;

                //vector 2
                v2.x = p3.x - p2.x;
                v2.y = p3.y - p2.y;
                v2.z = p3.z - p2.z;
        
                //cross product v1 X v2
                calCrossproduct(v1, v2, nv);
                //Plane formula a(x-x0)+b(y-y0)+c(z-z0)+d=0
                //Let x0,y0,z0 be one of the three points, then d = 0.
                final_vec.x = -1.0 * nv.x / nv.z;
                final_vec.y = -1.0 * nv.y / nv.z;
                final_vec.z = -1.0 * final_vec.x * p2.x + (-1.0 * final_vec.y * p2.y) + p2.z;
                
                myVectors.push_back(final_vec);
        }
        
        return myVectors;
}


inline int solver(CircuitGraph& graphIn, ExtendedVerilogParser* vpPtr, ExtendedSdcParser* sdcPtr, ExtendedLibParser* lpPtr) {
        
        unsigned long i, j, k;
        IloEnv env;
        time_t start,end;
        //time (&start);
        
        //define variables.........................
        IloModel model(env);
        
        const unsigned int numOptions = 30;
        unsigned long numGates = boost::num_vertices(graphIn);
        
        boost::graph_traits<CircuitGraph>::vertex_iterator vi, vi_end ;
	boost::graph_traits<CircuitGraph>::out_edge_iterator oi, oi_end;
        
        long count = 0;
        for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi)
        {
                vertex_descriptor in = (*vi);
                if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                        count++;
        }
        numGates -= count;
        
        /*-------------define decision variables--------------*/
        //define yi
        //Output capacitance of node i
        IloNumVarArray yi(env, numGates, 0, IloInfinity, ILOFLOAT);
        
        //define Xoi
        //Options for a gate, o for option, i for gate
        NumVarMatrix Xoi(env, numGates);
        for (i = 0; i < numGates; i++) 
                Xoi[i] = IloNumVarArray(env, numOptions, 0, 1, ILOINT);
        
        //define slew and delay variables. Assuming rising and falling
        //situation is exactly the same.
        count = 0;
        for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi){
                
                vertex_descriptor in = (*vi);
                
                if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                        count += 1;
                else
                        count += boost::out_degree(in, graphIn) + boost::in_degree(in, graphIn);
        }
        
        const long numEdges = count;
        //define Si (slew)  For the output of a gate
        IloNumVarArray Si(env, numEdges, 0, IloInfinity, ILOFLOAT);
        //define Di (slew)  For the output of a gate
        IloNumVarArray Di(env, numEdges, 0, IloInfinity, ILOFLOAT);
        
        const double M = numeric_limits<double>::max();
        string inputPin, outputPin;
        double inputDelay, inputSlew, outputLoad;
        vector<netConnectivity>::iterator it_vec;
        vector<vertex_descriptor>::iterator it_vertex;
        vector<LibParserPinInfo>::iterator it_pin;
        
        CircuitGraph::adjacency_iterator adj_vi, adj_vi_end ;
        CircuitGraph::inv_adjacency_iterator in_adj_vi, in_adj_vi_end ;
        
        //add object, constraints and solve
        try{
                //add objective expression
                IloExpr Power(env);
                std::vector<std::string> cellNames;
                
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi){
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1){
                                graphIn[ in ].gate_index = -1;
                                continue;
                        }
                        else{
                                graphIn[ in ].gate_index = i;
                                i++;
                        }
                }
                
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi)
                {
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                                continue;
                        
                        assert(graphIn[ in ].listOfCells.size() == numOptions);
                        for(j = 0; j < numOptions; j++)
                                Power += lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[j]].leakagePower * Xoi[i][j];
                        
                        i++;
                }
                
                IloObjective obj = IloMinimize(env, Power);
                model.add(obj); 
                Power.end();
                
                //------------------------------------------------adding constraints-------------------------------------------------
                //sum Xoi equals to 1
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi)
                {
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                                continue;
                        
                        IloExpr expr1(env,0);
                        for(j = 0; j < numOptions; j++)
                                expr1 += Xoi[i][j]; 
                        
                        model.add(expr1 == 1);
                        i++;
                }
                
                //yi equals to summation of the output loading
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi)
                {
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                                continue;
                        
                        IloExpr expr1(env,0);
                        outputPin = "o";
                        
                        for(it_vec = vpPtr->verilogConnectivity.begin(); it_vec != vpPtr->verilogConnectivity.end(); it_vec++)
                                if(it_vec->netName == graphIn[ in ].mapPinNetDirection[outputPin].first)
                                        break;
                        
                        assert(it_vec != vpPtr->verilogConnectivity.end());
                        
                        k = 0;
                        netConnectivity myNet = vpPtr->verilogConnectivity[distance(vpPtr->verilogConnectivity.begin(), it_vec)];
                        
                        for(it_vertex = myNet.targetVertices.begin(); it_vertex != myNet.targetVertices.end(); it_vertex++)
                        {
                                //check for this case in netconnectivity
                                if(graphIn[ *it_vertex ].gateType == 1)
                                {
                                        outputLoad = sdcPtr->outputLoads[graphIn[ *it_vertex ].instanceName];
                                        assert(outputLoad == 4.0);
                                        expr1 += outputLoad; 
                                }
                                else
                                {
                                        for(j = 0; j < numOptions; j++){
                                                for(it_pin = lpPtr->mapStandardLibraryCells[graphIn[ *it_vertex ].listOfCells[j]].pins.begin();
                                                    it_pin != lpPtr->mapStandardLibraryCells[graphIn[ *it_vertex ].listOfCells[j]].pins.end(); it_pin++)
                                                        if(it_pin->name == myNet.targetPins[k]){
                                                                Power += it_pin->capacitance * Xoi[i][j];
                                                                break;
                                                        }
                                                
                                                assert(it_pin != lpPtr->mapStandardLibraryCells[graphIn[ *it_vertex ].listOfCells[j]].pins.end());
                                        }
                                }

                                k++;
                        }
                        
                        model.add(expr1 == yi[i]);
                        i++;
                }
                
                //slew constraint  Si >= Approximated Expression
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi){
                        
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                                continue;
                        
                        for(it_pin = lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[0]].pins.begin();
                            it_pin != lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[0]].pins.end(); it_pin++)
                        {
                                if(it_pin->name == "o")
                                        continue;
                                
                                inputPin = it_pin->name;
                                for(it_vec = vpPtr->verilogConnectivity.begin(); it_vec != vpPtr->verilogConnectivity.end(); it_vec++)
                                        if(it_vec->netName == graphIn[ in ].mapPinNetDirection[inputPin].first)
                                                break;
                                                        
                                assert(it_vec != vpPtr->verilogConnectivity.end());
                                netConnectivity myNet = vpPtr->verilogConnectivity[distance(vpPtr->verilogConnectivity.begin(), it_vec)];
                                
                                for(j = 0; j < numOptions; j++){
                                        
                                        IloExpr expr1(env,0);
                                        IloExpr expr2(env,0);
                                        if(graphIn[ myNet.sourceVertex ].gateType == 0){
                                                vector<vec> approxTerms;
                                                approxTerms = lutApproximation(make_pair(0, 0), 6, 7, 
                                                                               lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[j]], inputPin, 1);
                                                
                                                inputSlew = sdcPtr->inputDrivers[graphIn[ myNet.sourceVertex ].instanceName].inputTransFall;
                                                assert(inputSlew == sdcPtr->inputDrivers[graphIn[ myNet.sourceVertex ].instanceName].inputTransRise);
                                                assert(inputSlew == 80.0);
                                                expr1 = approxTerms[0].x * yi[i] + approxTerms[0].y * inputSlew + approxTerms[0].z;
                                                expr2 = approxTerms[1].x * yi[i] + approxTerms[1].y * inputSlew + approxTerms[1].z;
                                        }
                                        else{
                                                vector<vec> approxTerms;
                                                approxTerms = lutApproximation(make_pair(0, 0), 6, 7, 
                                                                               lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[j]], inputPin, 1);
                                                
                                                expr1 = approxTerms[0].x * yi[i] + approxTerms[0].y * Si[graphIn[ myNet.sourceVertex ].gate_index] + approxTerms[0].z;
                                                expr2 = approxTerms[1].x * yi[i] + approxTerms[1].y * Si[graphIn[ myNet.sourceVertex ].gate_index] + approxTerms[1].z;
                                        }
                                        
                                        model.add(Si[i] >= expr1  - M * (1 - Xoi[i][j]));
                                        model.add(Si[i] >= expr2  - M * (1 - Xoi[i][j]));
                                }
                        }
                                
                        i++;
                }
                
                //delay constraint  Di >= Approximated Expression
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi){
                        
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                                continue;
                        
                        for(it_pin = lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[0]].pins.begin();
                            it_pin != lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[0]].pins.end(); it_pin++)
                        {
                                if(it_pin->name == "o")
                                        continue;
                                
                                inputPin = it_pin->name;
                                for(it_vec = vpPtr->verilogConnectivity.begin(); it_vec != vpPtr->verilogConnectivity.end(); it_vec++)
                                        if(it_vec->netName == graphIn[ in ].mapPinNetDirection[inputPin].first)
                                                break;
                                
                                assert(it_vec != vpPtr->verilogConnectivity.end());
                                netConnectivity myNet = vpPtr->verilogConnectivity[distance(vpPtr->verilogConnectivity.begin(), it_vec)];
                                
                                for(j = 0; j < numOptions; j++){
                                        
                                        IloExpr expr1(env,0);
                                        IloExpr expr2(env,0);
                                        if(graphIn[ myNet.sourceVertex ].gateType == 0){
                                                vector<vec> approxTerms;
                                                approxTerms = lutApproximation(make_pair(0, 0), 6, 7, 
                                                                               lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[j]], inputPin, 0);
                                                
                                                inputDelay = sdcPtr->inputDelays[graphIn[ myNet.sourceVertex ].instanceName];
                                                assert(inputDelays == 0.0);
                                                expr1 = approxTerms[0].x * yi[i] + approxTerms[0].y * inputDelay + approxTerms[0].z;
                                                expr2 = approxTerms[1].x * yi[i] + approxTerms[1].y * inputDelay + approxTerms[1].z;
                                        }
                                        else{
                                                vector<vec> approxTerms;
                                                approxTerms = lutApproximation(make_pair(0, 0), 6, 7, 
                                                                               lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[j]], inputPin, 1);
                                                
                                                expr1 = approxTerms[0].x * yi[i] + approxTerms[0].y * Di[graphIn[ myNet.sourceVertex ].gate_index] + approxTerms[0].z;
                                                expr1 = approxTerms[1].x * yi[i] + approxTerms[1].y * Di[graphIn[ myNet.sourceVertex ].gate_index] + approxTerms[1].z;
                                        }
                                        
                                        model.add(Di[i] >= Di[graphIn[ myNet.sourceVertex ].gate_index] + expr1  - M * (1 - Xoi[i][j]));
                                        model.add(Di[i] >= Di[graphIn[ myNet.sourceVertex ].gate_index] + expr2  - M * (1 - Xoi[i][j]));
                                }
                        }
                        
                        i++;
                }
                
                //note that we don't consider the output delay since it is
                //pre-assumed to be 0.0
                
                //bound slew
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi){
                        
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                                continue;
                        
                        model.add(Si[graphIn[ in ].gate_index] <= 300.0);
                        i++;
                }
                
                //bound fanout capacitance
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi){
                        
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                                continue;
                        
                        IloExpr expr1(env,0);
                        for(j = 0; j < numOptions; j++){
                                for(it_pin = lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[j]].pins.begin(); it_pin != lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[j]].pins.end(); it_pin++)
                                {
                                        if(it_pin->name == "o"){
                                                expr1 += it_pin->maxCapacitance * Xoi[i][j];
                                                break;
                                        }
                                }
                        }
                        
                        model.add(yi[graphIn[ in ].gate_index] <= expr1);
                        i++;
                }
                
                //----------------------------------------------------solve and optimize-----------------------------------------------------
                IloCplex cp(model);
                cp.setParam(cp.EpGap,0.20);
                cp.setParam(cp.TiLim,60);
                cp.setParam(cp.TreLim,6e9);
                cp.setParam(cp.MemoryEmphasis,true);
                cp.extract(model);
                
                cp.solve();
                
                //ofstream logfile("my.log");
                //cp.setOut(logfile);		  
                
                //----------------------------------------------------print out solution-----------------------------------------------------
                cout << std::endl << "Best Optimal value: " << cp.getBestObjValue() << std::endl;		    
                cout << std::endl << "Optimal value: " << cp.getValue(obj) << std::endl;
                cout << cp.getStatus() << std::endl;
                
                string filename("solution");
                cout<<"output file name is "<<filename<<endl;
                ofstream out(filename.c_str());
                
                cout<<"Gate sizes are "<<endl;
                i = 0;
                for (boost::tie(vi, vi_end) = boost::vertices(graphIn) ; vi != vi_end ; ++vi){
                        
                        vertex_descriptor in = (*vi);
                        if(graphIn[ in ].gateType == 0 || graphIn[ in ].gateType == 1)
                                continue;
                        
                        out << "Gate: " << i << "\tSize:" << Xoi[i] << "\t"; //<< lpPtr->mapStandardLibraryCells[graphIn[ in ].listOfCells[Xoi[i]]].name;
                        out << "Delay: " << Di[i] << "\tSlew: " << Si[i] << "\tLoading: " << yi[i] << endl;
                        i++;
                }
                
                                
                out.close();
                cp.end();
                model.end();
        }
        catch (IloException& ex) {
                env.out() << "Error: " << ex << std::endl;
        }
        
        env.end();        
        return 0;
}
